use super::{
    err, err_value, get_connect, json, ok, throw_err, write_bool, write_float, write_str,
    write_val, Bytes, Connect, ECode, MapSv, RNull, Resp, VBase, VMap, Value, VecV,
};
use rseip::{
    client::AbService,
    precludes::{
        ab_eip::{value::TagType, PathParser, TagValue},
        AbEipClient, EPath,
    },
    ClientError,
};
use std::collections::HashMap;
use std::time::Instant;
use tracing::{info, instrument};

macro_rules! read_tag {
    ($stream: expr, $tag: expr, $c: expr, $ty: ty) => {
        if $c > 0 {
            let v: TagValue<Vec<$ty>> = $stream.read_tag($tag).await?;
            let mut val = v.value;
            val.truncate($c as usize);
            Ok(json!(val))
        } else {
            let v: TagValue<$ty> = $stream.read_tag($tag).await?;
            Ok(json!(v.value))
        }
    };
}

macro_rules! write_send_tag {
    ($stream: expr, $tag: expr, $val: expr, $tagty: expr) => {
        let plc_tag = match EPath::parse_tag($tag) {
            Ok(v) => v,
            Err(e) => {
                throw_err!(ECode::SendVal, e);
            }
        };

        let value = TagValue {
            tag_type: $tagty,
            value: $val,
        };

        if let Err(e) = $stream.write_tag(plc_tag, value).await {
            match e {
                ClientError::Io { .. } => {
                    // io error, close cip
                    return Err(json!({"e": e.to_string()}));
                }
                _ => {
                    throw_err!(ECode::SendVal, e);
                }
            }
        }
    };
}

macro_rules! write_tag_c {
    ($stream: expr, $tag: expr, $c: expr, $val: expr, $tagty: expr) => {
        if $c > 0 {
            for (i, v) in $val.iter().enumerate() {
                let t = format!("{}.{}", $tag, i);
                write_send_tag!($stream, t, *v, $tagty);
            }
        } else {
            write_send_tag!($stream, $tag, $val[0], $tagty);
        }
    };
}

macro_rules! write_tag {
    ($stream: expr, $tag: expr, $c: expr, $val: expr, $ty: ty, $tagty: expr) => {
        let v = write_val($c, $val)?;
        let v: Vec<$ty> = v.iter().map(|&x| x as $ty).collect();

        write_tag_c!($stream, $tag, $c, v, $tagty);
    };
}

pub struct Omron;

impl Omron {
    pub async fn do_cmd(&mut self, data: &MapSv, cmd: &str, client: &Connect) -> Resp {
        match cmd {
            "connect" => self.do_connect(data, client).await,
            "reconnect" => self.do_reconnect(data, client).await,
            "close" => self.do_close(data, client).await,
            "read" => self.do_read(data, client).await,
            "write" => self.do_write(data, client).await,
            _ => throw_err!(ECode::NoServe, "no omron serve"),
        }
    }

    #[instrument(skip_all, name = "write")]
    async fn do_write(&mut self, data: &MapSv, client: &Connect) -> Resp {
        let start = Instant::now();

        let id = data.ck_str("id")?;
        let cmd = data.ck_array("data")?;

        let con = get_connect(id, client)?;
        let mut plc = con.lock().await;

        let stream = match plc.get_cip() {
            Some(v) => v,
            None => {
                throw_err!(ECode::ReadVal, "connect error");
            }
        };

        for d in cmd.iter() {
            let d = d.ckobj()?;

            let tag = d.ck_str("k")?;
            let t = d.ck_str("t")?;
            let val = d.ck_array("v")?;
            let c = match d.k_i64("c") {
                Some(v) => v,
                None => 0,
            };

            if t == "c" {
                let mut iter = val.iter();

                if c > 0 {
                    let mut tmp = Vec::new();

                    for it in iter {
                        let v = it.ckarray()?;
                        tmp.push(v);
                    }

                    tmp.truncate(c as usize);

                    for (i, it) in tmp.iter().enumerate() {
                        let ptag = format!("{}[{}]", tag, i);

                        if let Err(e) = self.write_custom(stream, &ptag, it).await {
                            if let Some(_) = e.k_str("e") {
                                plc.close_cip().await;
                            }

                            throw_err!(ECode::SendVal, e);
                        }
                    }
                } else {
                    if let Some(v) = iter.next() {
                        let v = v.ckarray()?;

                        if let Err(e) = self.write_custom(stream, tag, v).await {
                            if let Some(_) = e.k_str("e") {
                                plc.close_cip().await;
                            }

                            throw_err!(ECode::SendVal, e);
                        }
                    }
                }
            } else {
                if let Err(e) = self.write_data(stream, t, c, val, tag).await {
                    if let Some(_) = e.k_str("e") {
                        plc.close_cip().await;
                    }

                    throw_err!(ECode::SendVal, e);
                }
            }
        }

        let duration = start.elapsed();
        info!("omron write time: {:?} \t in: {:?}", duration, data);

        ok!()
    }

    #[instrument(skip_all, name = "read")]
    async fn do_read(&mut self, data: &MapSv, client: &Connect) -> Resp {
        let start = Instant::now();

        let id = data.ck_str("id")?;
        let cmd = data.ck_array("data")?;

        let con = get_connect(id, client)?;

        let mut plc = con.lock().await;

        let stream = match plc.get_cip() {
            Some(v) => v,
            None => {
                throw_err!(ECode::ReadVal, "connect error");
            }
        };

        let mut ret = HashMap::new();

        for d in cmd.iter() {
            let d = d.ckobj()?;

            let tag = d.ck_str("k")?;
            let label = d.ck_str("m")?;
            let t = d.ck_str("t")?;
            let c = match d.k_i64("c") {
                Some(v) => v,
                None => 0,
            };
            let r = match d.k_bool("r") {
                Some(v) => v,
                None => false,
            };
            let val = d.k_i64("v");

            let val = if t == "c" {
                if c > 0 {
                    let mut val = Vec::new();

                    for i in 0..c {
                        let ptag = format!("{}[{}]", tag, i);

                        let v = match self.read_custom(stream, &ptag, d).await {
                            Ok(t) => t,
                            Err(e) => {
                                if let Some(_) = e.k_str("e") {
                                    plc.close_cip().await;
                                }

                                throw_err!(ECode::ReadVal, e);
                            }
                        };

                        val.push(v);
                    }

                    json!(val)
                } else {
                    match self.read_custom(stream, tag, d).await {
                        Ok(v) => v,
                        Err(e) => {
                            if let Some(_) = e.k_str("e") {
                                plc.close_cip().await;
                            }

                            throw_err!(ECode::ReadVal, e);
                        }
                    }
                }
            } else {
                match self.read_data(stream, t, c, tag, r, val).await {
                    Ok(v) => v,
                    Err(e) => match e {
                        ClientError::Io { .. } => {
                            plc.close_cip().await;
                            throw_err!(ECode::ReadVal, format!("tag: {tag}, error: {:?}", e));
                        }
                        _ => {
                            throw_err!(ECode::ReadVal, format!("tag: {tag}, error: {:?}", e));
                        }
                    },
                }
            };

            ret.insert(label, val);
        }

        let duration = start.elapsed();
        info!(
            "omron read time: {:?} \t in: {:?} \t out: {:?}",
            duration, data, ret
        );

        ok!(ret)
    }

    async fn do_close(&mut self, data: &MapSv, client: &Connect) -> Resp {
        let id = data.ck_str("id")?;
        let con = get_connect(id, client)?;

        let mut plc = con.lock().await;

        plc.close_cip().await;

        ok!()
    }

    async fn do_reconnect(&mut self, data: &MapSv, client: &Connect) -> Resp {
        let ip = data.ck_str("ip")?;
        let id = data.ck_str("id")?;

        let con = get_connect(id, client)?;

        let mut plc = con.lock().await;
        plc.close_cip().await;

        plc.set_cip(ip).await?;

        ok!()
    }

    async fn do_connect(&mut self, data: &MapSv, client: &Connect) -> Resp {
        let ip = data.ck_str("ip")?;
        let id = data.ck_str("id")?;

        let con = get_connect(id, client)?;

        let mut plc = con.lock().await;

        plc.set_cip(ip).await?;

        ok!()
    }

    async fn write_data(
        &self,
        stream: &mut AbEipClient,
        t: &str,
        c: i64,
        val: &VecV,
        tag: &str,
    ) -> RNull {
        match t {
            "bool" => {
                let v = write_bool(c, val)?;
                write_tag_c!(stream, tag, c, v, TagType::Bool);
            }
            "byte" => {
                write_tag!(stream, tag, c, val, i8, TagType::Sint);
            }
            "int16" => {
                write_tag!(stream, tag, c, val, i16, TagType::Int);
            }
            "int32" => {
                write_tag!(stream, tag, c, val, i32, TagType::Dint);
            }
            "int64" => {
                write_tag!(stream, tag, c, val, i64, TagType::Lint);
            }
            "float" => {
                let v = write_float(c, val)?;
                let v: Vec<f32> = v.iter().map(|&x| x as f32).collect();

                write_tag_c!(stream, tag, c, v, TagType::Real);
            }
            "string" => {
                let v = write_str(c, val)?;

                if c > 0 {
                    for (i, v) in v.iter().enumerate() {
                        let t = format!("{}.{}", tag, i);
                        write_send_tag!(stream, t, v.to_vec(), TagType::STRING);
                    }
                } else {
                    write_send_tag!(stream, tag, v[0].to_vec(), TagType::STRING);
                }
            }
            _ => {
                throw_err!(ECode::SendVal, format!("value type error: {}", t));
            }
        }

        Ok(())
    }

    async fn write_custom(&self, stream: &mut AbEipClient, tag: &str, data: &VecV) -> RNull {
        for d in data.iter() {
            let d = d.ckobj()?;

            let stag = d.ck_str("k")?;
            let t = d.ck_str("t")?;
            let v = d.ck_array("v")?;
            let c = match d.k_i64("c") {
                Some(v) => v,
                None => 0,
            };

            let stag = format!("{}.{}", tag, stag);

            self.write_data(stream, t, c, v, &stag).await?;
        }

        Ok(())
    }

    async fn read_custom(&self, stream: &mut AbEipClient, tag: &str, d: &MapSv) -> Resp {
        let sub = d.ck_array("v")?;
        let mut sub_val = HashMap::new();

        for sd in sub.iter() {
            let stag = sd.ck_str("k")?;
            let slable = sd.ck_str("m")?;
            let st = sd.ck_str("t")?;
            let sc = match sd.k_i64("c") {
                Some(v) => v,
                None => 0,
            };
            let r = match sd.k_bool("r") {
                Some(v) => v,
                None => false,
            };
            let val = sd.k_i64("v");

            let stag = format!("{}.{}", tag, stag);

            let sv = match self.read_data(stream, st, sc, &stag, r, val).await {
                Ok(v) => v,
                Err(e) => match e {
                    ClientError::Io { .. } => {
                        return Err(json!({ "e": format!("tag: {:?}, error: {:?}", &stag, e) }));
                    }
                    _ => {
                        throw_err!(ECode::ReadVal, format!("tag: {:?}, error: {:?}", &stag, e));
                    }
                },
            };

            sub_val.insert(slable, sv);
        }

        Ok(json!(sub_val))
    }

    fn eq_int(
        &self,
        val: Result<Value, ClientError>,
        org: Option<i64>,
    ) -> Result<Value, ClientError> {
        let val = val?;

        match org {
            Some(v) => match val.ki64() {
                Some(t) => {
                    let m = if v == t { true } else { false };
                    Ok(json!(m))
                }
                None => Ok(val),
            },
            None => Ok(val),
        }
    }

    async fn read_data(
        &self,
        stream: &mut AbEipClient,
        t: &str,
        c: i64,
        tag: &str,
        r: bool,
        val: Option<i64>,
    ) -> Result<Value, ClientError> {
        let plc_tag = EPath::parse_tag(tag)?;

        match t {
            "bool" => read_tag!(stream, plc_tag, c, bool),
            "byte" => read_tag!(stream, plc_tag, c, u8),
            "int16" => self.eq_int(read_tag!(stream, plc_tag, c, i16), val),
            "uint16" => self.eq_int(read_tag!(stream, plc_tag, c, u16), val),
            "int32" => self.eq_int(read_tag!(stream, plc_tag, c, i32), val),
            "uint32" => self.eq_int(read_tag!(stream, plc_tag, c, u32), val),
            "int64" => self.eq_int(read_tag!(stream, plc_tag, c, i64), val),
            "uint64" => self.eq_int(read_tag!(stream, plc_tag, c, u64), val),
            "float" => read_tag!(stream, plc_tag, c, f32),
            "double" => read_tag!(stream, plc_tag, c, f64),
            "string" => {
                if c > 0 {
                    let mut ret = Vec::new();

                    for i in 0..c {
                        let t = format!("{}.{}", tag, i);
                        let ptag = EPath::parse_tag(&t)?;
                        let s: TagValue<Bytes> = stream.read_tag(ptag).await?;

                        if !s.value.is_empty() {
                            if let Ok(v) = String::from_utf8(s.value.to_vec()) {
                                let v = v.trim_start_matches("#\u{0000}");
                                let mut v = v.trim_matches(char::is_control);
                                if r {
                                    v = v.trim();
                                }

                                ret.push(json!(v));
                            }
                        }
                    }

                    Ok(json!(ret))
                } else {
                    let s: TagValue<Bytes> = stream.read_tag(plc_tag).await?;

                    if !s.value.is_empty() {
                        if let Ok(v) = String::from_utf8(s.value.to_vec()) {
                            let v = v.trim_start_matches("#\u{0000}");
                            let mut v = v.trim_matches(char::is_control);
                            if r {
                                v = v.trim();
                            }

                            return Ok(json!(v));
                        }
                    }

                    Ok(json!(""))
                }
            }
            _ => Err(ClientError::Custom {
                kind: "Value type err",
                msg: rseip::String::Heap(t.to_string()),
            }),
        }
    }
}
